const Buffer = require('buffer').Buffer
const dgram = require('dgram')
const logger = require('./console-logger')

// 帧头(2)+帧版本（1）+帧总数（1）+帧序号（1）+帧长度(2)+备用（1）+设备ID（1）+命令码(2)+内容(N)+校验码(1)
// 1.0 帧头：0xF7,0x7F。
// 1.1 帧版本号：（1字节Hex码）
// 1.2 帧总数：（1字节Hex码）本次数据传输要传的总帧数。
// 1.3 帧序号：（1字节Hex码）当前传输帧的序号。
// 1.4 帧长度：（2字节Hex码）高字节在靠近帧头。包括“帧头”到“校验码”的所有字节数。
// 1.5 设备硬件状态码：（1字节Hex码）默认为0（备注：各硬件状态值详见硬件状态码表）
// 1.6 设备ID：（1字节Hex码）（备注：取值范围为1到255，0为内部测试使用，默认为1）
// 1.7 命令码（2字节Hex码）：命令代码。下详。
// 1.8 内容（N字节Hex码）：内容定义详见对应命令。
// 1.9 校验和（1字节Hex码）：仅计算从帧头到内容的校验和，计算方式为：以字节为单位进行加计算，计算出总和后再加1，取低8位。

const buf0 = [0xf7, 0x7f]
const buf1 = [0x01]
const buf2 = [0x01]
const buf3 = [0x01]
const buf4 = [0x00, 0x0c] // 帧长度,需计算
const buf5 = [0x00]
const buf6 = [0x01]
const buf7 = [0x00, 0x00] // 命令码,根据指令设置
const buf8 = [0x01] // 内容,根据指令设置
const buf9 = [0x00] // 校验帧,需要计算

const baseBuf = [0xF7, 0x7F, 0x01, 0x01, 0x01, 0x00, 0x0C, 0x00, 0x01]

function bufferToArray(buffer) {
    const array = []
    for (const b of buffer) {
        array.push(b)
    }
    return array
}

// 计算帧长度、校验帧
function finalBuf(action, content) {
    let buf = Buffer.concat([Buffer.from(baseBuf), action])
    if (content) {
        buf = Buffer.concat([buf, content, Buffer.from([0x00])])
    } else {
        buf = Buffer.concat([buf, Buffer.from([0x00])])
    }
    // 1.
    const length = buf.length
    const lenHigh = (length & 0xff00) >> 8
    const lenLow = (length & 0xff)
    buf[5] = lenHigh
    buf[6] = lenLow
    // 2.
    buf[length - 1] = calCrc(buf)

    return buf
}

function calCrc(buf) {
    let sum = 0
    for (const b of buf.values()) {
        sum += b 
    }
    return (sum + 1 & 0xff)
}

class Device {
    constructor(ip, port, ssid) {
        this.ip = ip
        this.port = port
        this.ssid = ssid
    }

    init() {
        // 存放响应命令
        this.responseBuf = null
        // 存放最后一次命令响应结果
        this.lastResponse = {}

        this.client = dgram.createSocket('udp4')
        // this.client.bind(() => {
        //     this.client.setBroadcast(true);
        // });
        // 消息监听
        this.client.on('message', (msg, rinfo) => {
            logger.debug('Got message:', msg)
            
            // 长红外学习查询结果长度原因分两次到达, 第二次<Buffer 00 00 00 00 00 00 00 00 00 00 00 00 00 ed>
            if (msg[0] === 0xf7 && msg[1] === 0x7f) {
                this.responseBuf = msg
                const realLen = (msg[5] << 8) + msg[6]
                if (realLen === msg.length) { // 数据收完整
                    this.responseHandle()
                }
                // this.responseHandle()
            } else {
                this.responseBuf = Buffer.concat([this.responseBuf, msg])
                this.responseHandle()
            }     
        });
    }

    close() {
        this.client.close()
    }

    sendAction(action, content) {
        if (!this.client) {
            logger.error('ERROR: Please device.init() first!')
            return
        } 
        const sendBuf = finalBuf(action, content)
        logger.debug('sendBuf:', sendBuf)
        this.client.send(sendBuf, this.port, this.ip, (err) => {
            err && logger.error(err);
        });
    }

    responseHandle() {
        const responseBuf = this.responseBuf
        const responseTypeCode = responseBuf[10]
        this.lastResponse = {
            type: responseTypeCode,
            status: 0,
            message: '',
            data: null
        }
        switch (responseTypeCode) {
            case 0x80:
                // 发起帧为任意帧数据，若该帧数据无法解析，则收到该错误反馈帧，若该帧数据可以解析，则返回对应的应答帧
                logger.debug('响应错误反馈,命令帧错误')
                break;
            case 0x81:
                // F7 7F 01 01 01 00 0E 00 01 00 81 07 00 11（校验码错误响应）
                if (responseBuf.length > 14) {
                    logger.debug('握手成功')
                    this.lastResponse.status = 1
                    this.lastResponse.message = '握手成功'
                } else {
                    logger.debug('握手失败: 校验码错误')
                    this.lastResponse.status = 0
                    this.lastResponse.message = '握手失败: 校验码错误'
                }
                break;
            case 0x82:
                const statusCode82 = responseBuf[12]
                switch (statusCode82) {
                    case 1:
                        logger.debug('设置连接WIFI: 成功')
                        break;
                    case 2:
                        logger.debug('设置连接WIFI: 失败')
                        break;
                    case 0:
                        logger.debug('设置连接WIFI: 其他错误')
                        break;
                    default:
                        break;
                }
                break;
            case 0x9c:
                // 1 字节状态码，1 字节数据状态码，N 字节IP,SSID,MAC(当状态码不为 0 时，N 字节内容为空)
                const statusCode9c = responseBuf[11]
                if (statusCode9c === 0) {
                    logger.debug('查询IP,SSID,MAC:', responseBuf.subarray(13, responseBuf.length - 1).toString())
                } else {
                    logger.debug('查询IP,SSID,MAC: 错误')
                }
                break;
            case 0x8e:
                // 1 字节状态码，1 字节结果状态值（1 查询成功， 0 其他错误）6 字节（依次为年(2 字节）月（1字节）日（1 字节）时（1 字节）分（1 字节））
                const statusCode8e = responseBuf[12]
                if (statusCode8e === 1) {
                    logger.debug('查询时间:', bufferToArray(responseBuf.subarray(13, responseBuf.length - 1)))
                } else {
                    logger.debug('查询时间: 错误')
                }
                break;      
            case 0x8f:
                // 1 字节状态码，1 字节结果状态值（1 查询成功， 0 其他错误）
                const statusCode8f = responseBuf[12]
                if (statusCode8f === 1) {
                    logger.debug('设置时间: 成功')
                } else {
                    logger.debug('设置时间: 错误')
                }
                break;   
            case 0xb3:
                // 1 字节状态码，1  字节设置结果状态码（1 设置成功，0 设置失败）
                if (responseBuf[11] === 0) {
                    const statusCodeb3 = responseBuf[12]
                    switch (statusCodeb3) {
                        case 1:
                            logger.debug('设置蜂鸣器状态: 成功')
                            break;
                        case 0:
                            logger.debug('设置蜂鸣器状态: 失败')
                            break;
                        default:
                            break;
                    }
                } else {
                    logger.debug('设置蜂鸣器状态: 错误')
                }
                break;          
            case 0xb4:
                // 1 字节状态码，1 字节查询结果状态码（1 为打开蜂鸣器，0 为关闭蜂鸣器）
                if (responseBuf[11] === 0) {
                    const statusCodeb4 = responseBuf[12]
                    switch (statusCodeb4) {
                        case 1:
                            logger.debug('查询蜂鸣器状态: 开')
                            break;
                        case 0:
                            logger.debug('查询蜂鸣器状态: 关')
                            break;
                        default:
                            break;
                    }
                } else {
                    logger.debug('查询蜂鸣器状态: 错误')
                }
                break;      
            case 0x86:
                if (responseBuf[11] === 0 && responseBuf[12] === 1) {
                    logger.debug('开始长红外学习: 成功')
                } else {
                    logger.debug('开始长红外学习: 错误')
                }
                break;    
            case 0x87:
                if (responseBuf[11] === 0 && responseBuf[12] === 1) {
                    logger.debug('停止长/短红外学习: 成功')
                } else {
                    logger.debug('停止长/短红外学习: 错误')
                }
                break;  
            case 0x88:
                if (responseBuf[11] === 0) {
                    if (responseBuf[12] === 1) {
                        const longRedCode = bufferToArray(responseBuf.subarray(13, responseBuf.length - 1))
                        logger.debug('查询长红外码学习结果数据: 成功')
                        this.lastResponse.status = 1
                        this.lastResponse.message = '查询长红外码学习结果数据: 成功'
                        this.lastResponse.data = longRedCode
                    } else {
                        logger.debug('查询长红外码学习结果数据: 无学习结果或者正在学习中')
                        this.lastResponse.status = 0
                        this.lastResponse.message = '查询长红外码学习结果数据: 无学习结果或者正在学习中'
                    }
                } else {
                    logger.debug('查询长红外码学习结果数据: 错误')
                    this.lastResponse.status = 0
                    this.lastResponse.message = '查询长红外码学习结果数据: 错误'
                }
                break;   
            case 0x89:
                if (responseBuf[11] === 0 && responseBuf[12] === 1) {
                    logger.debug('发送长红外码: 成功')
                } else {
                    logger.debug('发送长红外码: 错误')
                }
                break;
            case 0xb0:
            if (responseBuf[11] === 0 && responseBuf[12] === 1) {
                logger.debug('开始短红外学习: 成功')
            } else {
                logger.debug('开始短红外学习: 错误')
            }
            break;     
            case 0xb1:
                if (responseBuf[11] === 0) {
                    if (responseBuf[12] === 1) {
                        const shortRedCode = bufferToArray(responseBuf.subarray(13, responseBuf.length - 1))
                        logger.debug('查询短红外码学习结果数据: 成功')
                        
                        this.lastResponse.status = 1
                        this.lastResponse.message = '查询短红外码学习结果数据: 成功'
                        this.lastResponse.data = shortRedCode
                    } else {
                        logger.debug('查询短红外码学习结果数据: 无学习结果或者正在学习中')
                        this.lastResponse.status = 0
                        this.lastResponse.message = '查询短红外码学习结果数据: 无学习结果或者正在学习中'
                    }
                } else {
                    logger.debug('查询短红外码学习结果数据: 错误')
                    this.lastResponse.status = 0
                    this.lastResponse.message = '查询短红外码学习结果数据: 错误'
                }
                break;   
            case 0xb2:
                if (responseBuf[11] === 0 && responseBuf[12] === 1) {
                    logger.debug('发送短红外码: 成功')
                } else {
                    logger.debug('发送短红外码: 错误')
                }
                break;
            default:
                break;
        }
    }
    
    // 握手指令
    shakeHand() {
        const action = Buffer.from([0x00, 0x01])
        const content = Buffer.concat([Buffer.from([0xaa]), Buffer.from(this.ssid)])  // 固定值0xAA+12位SSID
        this.sendAction(action, content)
        logger.debug('Action: 握手指令')
    }

    // 设置连接WIFI
    setWifi(ssid, pwd) {
        const action = Buffer.from([0x00, 0x02])
        const content = Buffer.from(`"${ssid}","${pwd}"`)
        this.sendAction(action, content)
        logger.debug('Action: 设置连接WIFI')
    }

    // 查询IP,SSID,MAC
    getInfo() {
        const action = Buffer.from([0x00, 0x1c])
        const content = Buffer.from(this.ssid)
        this.sendAction(action, content)
        logger.debug('Action: 查询IP,SSID,MAC')
    }

    // 查询时间
    getTime() {
        const action = Buffer.from([0x00, 0x0e])
        this.sendAction(action)
        logger.debug('Action: 查询时间')
    }

    // 设置时间
    setTime(datetime) {
        const action = Buffer.from([0x00, 0x0f])
        const now = datetime
        const year = now.getFullYear() + ''
        const month = now.getMonth() + 1
        const date = now.getDate()
        const hour = now.getHours()
        const minutes = now.getMinutes()
        const content = Buffer.from([parseInt(year.substring(0,2)), parseInt(year.substring(2,4)), month, date, hour, minutes])
        this.sendAction(action, content)
        logger.debug('Action: 查询时间')
    }

    // 设置蜂鸣器状态 !!!命令成功,实际无效
    setBee(state) { 
        const action = Buffer.from([0x00, 0x33])
        const content = Buffer.from([state]) // 1开0关
        this.sendAction(action, content)
        logger.debug('Action: 设置蜂鸣器状态')
    }

    // 查询蜂鸣器状态
    getBee() {
        const action = Buffer.from([0x00, 0x34])
        this.sendAction(action)
        logger.debug('Action: 查询蜂鸣器状态')
    }

    // 开始长红外学习
    startLongRedLearn() {
        const action = Buffer.from([0x00, 0x06])
        this.sendAction(action)
        logger.debug('Action: 开始长红外学习')
    }

    // 停止长/短红外学习
    stopRedLearn() {
        const action = Buffer.from([0x00, 0x07])
        this.sendAction(action)
        logger.debug('Action: 停止长/短红外学习')
    }

    // 查询长红外码学习结果数据
    getLongRedLearn() {
        const action = Buffer.from([0x00, 0x08])
        this.sendAction(action)
        logger.debug('Action: 查询长红外码学习结果数据')
    }    

    // 发送长红外码
    sendLongRed(code) {
        const action = Buffer.from([0x00, 0x09])
        const content = Buffer.from(code)
        this.sendAction(action, content)
        logger.debug('Action: 发送长红外码')
    }

    // 开始短红外学习
    startShortRedLearn() {
        const action = Buffer.from([0x00, 0x30])
        this.sendAction(action)
        logger.debug('Action: 开始短红外学习')
    }

    // 查询短红外码学习结果数据
    getShortRedLearn() {
        const action = Buffer.from([0x00, 0x31])
        this.sendAction(action)
        logger.debug('Action: 查询短红外码学习结果数据')
    }    

    // 发送短红外码
    sendShortRed(code) {
        const action = Buffer.from([0x00, 0x32])
        const content = Buffer.from(code)
        this.sendAction(action, content)
        logger.debug('Action: 发送短红外码')
    }

// 红外和射频查询指令有反馈，其他红外和射频指令屏蔽反馈。
// 当启动红外和 RF 射频学习后，只有设备收到结束学习指令或者收到查询指令并返回学习到的数据后，才重新接受其他的红外和 RF射频发射指令，否则需在收不到
// 查询指令两秒后才能重新接收

//!!! 开始学习LED2红灯亮起,接收到信号LED2熄灭!!!

// 长红外码的指令适用于空调等红外码长度比较长的设备。
// 短红外码的指令适用电视，机顶盒，空调扇等红外码长度比较短的设备。
}

const devices = {}

module.exports = {
    getDevice: function(device) {
        let instance = devices[device.id]
        if (!instance) {
            
            logger.debug('init device:', device)

            instance = new Device(device.ip, 8888, device.ssid)
            instance.init()
    
            devices[device.id] = instance
        }
        return instance
    }
}